package com.pv.bfc.dao;

import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.FileSystemResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.FileCopyUtils;

import com.pv.bfc.model.Document;
import com.pv.bfc.web.DocumentUpload;

public class DocumentFileRepository implements DocumentRepository {

	private Log log = LogFactory.getLog(DocumentFileRepository.class);

	private String repositoryRoot = System.getProperty("bfc.root");

	private boolean deleteOnShutdown = true;

	private EntityManager entityManager;

	private DataFieldMaxValueIncrementer documentIdIncrementor;

	private enum Rotation {

		LEFT(Math.toRadians(-90)), RIGHT(Math.toRadians(90));

		private final double angle;

		Rotation(double angle) {
			this.angle = angle;
		}

		@SuppressWarnings("unused")
		public double getAngle() {
			return angle;
		}

	}

	public void deleteDocument(Document documentToDelete) {
		log.info("IN - DocumentFileRepository - deleteDocument");
		File fileToDelete = new File(this.repositoryRoot
				+ documentToDelete.getPath());
		fileToDelete.delete();
		log.info("OUT - DocumentFileRepository - deleteDocument");
	}

	public void rotateDocumentLeft(Document document) {
		log.info("IN - DocumentFileRepository - rotateDocumentLeft");
		rotateDocument(document, Rotation.LEFT);
		log.info("OUT - DocumentFileRepository - rotateDocumentLeft");
	}

	public void rotateDocumentRight(Document document) {
		log.info("IN - DocumentFileRepository - rotateDocumentRight");
		rotateDocument(document, Rotation.RIGHT);
		log.info("OUT - DocumentFileRepository - rotateDocumentRight");
	}

	private void rotateDocument(Document document, Rotation rotation) {
		log.info("IN - DocumentFileRepository - rotateDocument");
		ResourceLoader loader = new FileSystemResourceLoader();
		Resource resource = loader.getResource(repositoryRoot
				+ document.getLocation());

		BufferedImage img;
		try {
			img = ImageIO.read(resource.getFile());
			BufferedImage result = tilt(img, rotation);
			String fileName = document.getFileName();
			ImageIO.write(result,
					fileName.substring(fileName.indexOf('.') + 1), new File(
							resource.getFile().toURI()));
		} catch (IOException e) {
			throw new RuntimeException("Failed to rotate image.", e);
		}
		log.info("OUT - DocumentFileRepository - rotateDocument");
	}

	@SuppressWarnings("unused")
	private BufferedImage createThumb(BufferedImage image) {
		return image;
	}

	public static BufferedImage tilt(BufferedImage image, Rotation rotation) {
		double sin = Math.abs(Math.sin(rotation.angle)), cos = Math.abs(Math
				.cos(rotation.angle));
		int w = image.getWidth(), h = image.getHeight();
		int neww = (int) Math.floor(w * cos + h * sin), newh = (int) Math
				.floor(h * cos + w * sin);
		GraphicsConfiguration gc = getDefaultConfiguration();
		BufferedImage rotated = gc.createCompatibleImage(neww, newh);
		Graphics2D g = rotated.createGraphics();
		g.translate((neww - w) / 2, (newh - h) / 2);
		g.rotate(rotation.angle, w / 2, h / 2);
		g.drawRenderedImage(image, null);
		g.dispose();
		return rotated;
	}

	public static GraphicsConfiguration getDefaultConfiguration() {
		GraphicsEnvironment ge = GraphicsEnvironment
				.getLocalGraphicsEnvironment();
		GraphicsDevice gd = ge.getDefaultScreenDevice();
		return gd.getDefaultConfiguration();
	}

	@SuppressWarnings("deprecation")
	public UploadStatus storeDocument(DocumentUpload upload) {
		log.info("IN - DocumentFileRepository - storeDocument");
		int nextDocId = getDocumentIdIncrementor().nextIntValue();

		// Check if all minimum requirements are met.
		if (upload.getFolderId() == null || upload.getUpload() == null
				|| repositoryRoot == null) {
			throw new IllegalArgumentException(
					"A document cannot be stored if the folder ID or file is not provided.");
		}

		StringBuffer target = new StringBuffer();
		target.append("folders");
		target.append(File.separator);
		target.append(upload.getFolderId());
		target.append(File.separator);

		// Set the relative file location here.
		String path = target.toString();

		target.insert(0, this.repositoryRoot);

		// Create folder if it doesn't exist.
		File dir = new File(target.toString());
		if (!dir.exists()) {
			dir.mkdirs();
		}

		String fileName = upload.getUpload().getOriginalFilename();
		if (fileName == null || "".equals(fileName)) {
			fileName = upload.getUpload().getName();
		}
		target.append(fileName);

		// Check if the document exists.
		File targetFile = new File(target.toString());
		if (targetFile.exists()) {
			if (log.isInfoEnabled()) {
				log.info(targetFile + " already exists.");
			}
			return UploadStatus.EXISTS;
		}

		// Mark for automatic deletion on JVM shutdown.
		if (deleteOnShutdown) {
			targetFile.deleteOnExit();
		}

		// Check if this is a valid image.
		try {
			if (ImageIO.read(upload.getUpload().getInputStream()) == null) {
				return UploadStatus.INVALID;
			}
		} catch (IOException e1) {
			e1.printStackTrace();
			return UploadStatus.FAILED;
		}

		// Write the file to the document repository.
		try {
			if (log.isInfoEnabled()) {
				log.info("Copying file to " + targetFile.toURL());
			}
			FileCopyUtils.copy(upload.getUpload().getBytes(), targetFile);
		} catch (IOException e) {
			e.printStackTrace();
			return UploadStatus.FAILED;
		}

		// Get a reference to the document object to populate with data coming
		// from the file.
		Document document = upload.getDocument();
		if (upload.getDocument() == null) {
			document = new Document();
			upload.setDocument(document);
		}
		document.setDocId(nextDocId);
		document.setFileName(fileName);
		document.setPath(path);
		document.setSize(upload.getUpload().getSize());

		log.info("OUT - DocumentFileRepository - storeDocument");

		return UploadStatus.SUCCESS;
	}

	@Transactional
	public void persistDocument(Document document) {
		log.info("IN - DocumentFileRepository - persistDocument");
		entityManager.persist(document);
		log.info("OUT - DocumentFileRepository - persistDocument");
	}

	public void setRepositoryRoot(String repositoryRoot) {
		this.repositoryRoot = repositoryRoot;
	}

	public void setDeleteOnShutdown(boolean deleteOnShutdown) {
		this.deleteOnShutdown = deleteOnShutdown;
	}

	public DataFieldMaxValueIncrementer getDocumentIdIncrementor() {
		return documentIdIncrementor;
	}

	public void setDocumentIdIncrementor(
			DataFieldMaxValueIncrementer documentIdIncrementor) {
		this.documentIdIncrementor = documentIdIncrementor;
	}

	@PersistenceContext
	public void setEntityManager(EntityManager entityManager) {
		this.entityManager = entityManager;
	}
}
